﻿Imports Framework.IO

Friend Class Handler_PackagingDaemon
    Private Shared mDaemon As Threading.Thread

    Public Shared Sub Start()
        mDaemon = New Threading.Thread(AddressOf Daemon)
        mDaemon.IsBackground = True
        mDaemon.Name = "Backup Packaging Daemon Thread"
        mDaemon.Priority = Threading.ThreadPriority.Lowest
        mDaemon.Start()
    End Sub
    Public Shared Sub [Stop]()
        If mDaemon IsNot Nothing Then
            mDaemon.Abort()
            mDaemon.Join(10000)
            mDaemon = Nothing
        End If
    End Sub

    Private Shared mEventArgs As New Manager.PackagingDaemonActivityEventArgs

    Private Shared Sub Daemon()
        Try
            Do
                Try
                    Dim clsWorkItem As WorkItem = JobQueue.GetNext
                    If clsWorkItem Is Nothing Then
                        If mEventArgs.Action <> "Idle" Then
                            mEventArgs.Action = "Idle"
                            mEventArgs.FilePath = Nothing
                            mEventArgs.FileSize = 0
                            mEventArgs.ActionBytesProcessed = 0
                            mEventArgs.Job = Nothing
                            mEventArgs.ActionCompletedPercent = 0
                            Manager.OnPackagingDaemonActivity(mEventArgs)
                        End If
                        Threading.Thread.Sleep(5000)
                    Else
                        Process(clsWorkItem)
                    End If
                Catch ex As Threading.ThreadAbortException
                    If TypeOf ex.ExceptionState Is Entities.Backup.Job Then
                        Threading.Thread.ResetAbort()
                    Else
                        Exit Do
                    End If
                Catch ex As Exception
                    Manager.OnLogException(ex)
                End Try
            Loop
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            Manager.OnLogException(ex)
        End Try
    End Sub

    Private Shared Function GetTempFileName() As String
        Dim strRoot As String
        If String.IsNullOrEmpty(Settings.WorkingFolder) Then
            strRoot = IO.Path.Combine(System.IO.Path.GetTempPath, "B2B_Backup_CompressAndEncrypt")
        ElseIf IO.Directory.Exists(Settings.WorkingFolder) Then
            strRoot = IO.Path.Combine(Settings.WorkingFolder, "CompressAndEncrypt")
        Else
            strRoot = IO.Path.Combine(System.IO.Path.GetTempPath, "B2B_Backup_CompressAndEncrypt")
        End If
        If Not IO.Directory.Exists(strRoot) Then
            IO.Directory.CreateDirectory(strRoot)
        End If
        Return IO.Path.Combine(strRoot, IO.Path.GetRandomFileName & ".b2bb")
    End Function

    Private Shared Sub Process(ByVal workItem As WorkItem)
        Try
            'Manager.OnJobInformation(workItem.Job, String.Format("Preparing file ""{0}"" for storage", workItem.FileInfo.FullName))

            mEventArgs.Action = "Analyze"
            mEventArgs.ActionCompletedPercent = 0
            mEventArgs.FilePath = workItem.FileInfo.FullName
            mEventArgs.FileSize = workItem.FileInfo.Length
            mEventArgs.Job = workItem.Job
            Manager.OnPackagingDaemonActivity(mEventArgs)

            'calculate new version number
            Dim iVersion As Integer = 0
            If workItem.StoredVersions IsNot Nothing Then
                For Each clsStoredFile As Entities.Backup.StoredFile In workItem.StoredVersions
                    iVersion = Math.Max(clsStoredFile.Version, iVersion)
                Next
            End If
            iVersion += 1

            Dim strTmpFile As String = Nothing
            Try
                Using clsSrcFile As IO.FileStream = workItem.FileInfo.OpenRead
                    strTmpFile = GetTempFileName()
                    Using clsDstFile As New IO.FileStream(strTmpFile, IO.FileMode.Create, IO.FileAccess.Write)
                        'write Buddy2Buddy Backup header
                        Framework.IO.BlockStreamWriter.WriteFixedLengthAscii(clsDstFile, "B2BB")
                        Framework.IO.BlockStreamWriter.Write(clsDstFile, CShort(1)) 'file structure version 

                        Using clsDstStream As IO.Stream = CreateCryptoStreamAndWriteHeader(workItem.Job, clsDstFile)

                            'ok, now write meta data, such as original filename , attributes, etc
                            Dim bMetaBlock() As Byte = CreateMetaBlock(workItem.FileInfo, iVersion)
                            Framework.IO.BlockStreamWriter.Write(clsDstStream, bMetaBlock)

                            Compression_WriteToOutput(workItem.Job, clsDstStream, clsSrcFile)
                        End Using
                    End Using
                End Using
                'ok, we should now have a file in strTmpFile ready for transport...
                Handler_UploadFile.QueueUpload(workItem.Job, strTmpFile, workItem.FileInfo, workItem.StoredVersions)
            Finally
                mEventArgs.ActionCompletedPercent = 100
                Manager.OnPackagingDaemonActivity(mEventArgs)

                If strTmpFile IsNot Nothing AndAlso IO.File.Exists(strTmpFile) Then IO.File.Delete(strTmpFile)
            End Try
        Finally
            JobQueue.WorkItemFinished(workItem)
        End Try
    End Sub

    Private Shared Sub Compression_WriteToOutput(ByVal job As Entities.Backup.Job, ByVal dest As IO.Stream, ByVal src As IO.Stream)
        Select Case SafeGetCompressionType(job.CompressionProperties)
            Case Entities.Backup.CompressionType.None
                Compression_None_WriteToOutput(job, dest, src)
            Case Entities.Backup.CompressionType.LZMA
                Compression_LZMA_WriteToOutput(job, dest, src)
            Case Else
                Throw New NotImplementedException
        End Select
    End Sub

    Private Shared Sub Compression_LZMA_WriteToOutput(ByVal job As Entities.Backup.Job, ByVal dest As IO.Stream, ByVal src As IO.Stream)
        Framework.IO.BlockStreamWriter.WriteFixedLengthAscii(dest, "LZMA")
        Framework.IO.BlockStreamWriter.Write(dest, src.Length)

        Dim clsEncoder As New SevenZip.Compression.LZMA.Encoder()
        clsEncoder.WriteCoderProperties(dest)
        Dim lLen As Long = src.Length - src.Position
        BlockStreamWriter.Write(dest, lLen)

        If lLen > 10 * 1024 * 1024 Then
            'use file buffer
            Dim strTmp As String = IO.Path.GetTempFileName
            Try
                Using clsTmp As IO.Stream = New IO.FileStream(strTmp, IO.FileMode.OpenOrCreate, IO.FileAccess.ReadWrite)
                    mEventArgs.Action = "Compressing"
                    mEventArgs.ActionCompletedPercent = 0
                    mEventArgs.ActionBytesProcessed = 0
                    Manager.OnPackagingDaemonActivity(mEventArgs)
                    Dim clsP As New LZMACompressProgress(src.Length)
                    clsEncoder.Code(src, clsTmp, src.Length, Long.MaxValue, clsP)

                    mEventArgs.Action = GetActivityCopyToDestinationActionText(job)
                    mEventArgs.ActionCompletedPercent = 0
                    mEventArgs.ActionBytesProcessed = 0
                    Manager.OnPackagingDaemonActivity(mEventArgs)
                    clsTmp.Seek(0, IO.SeekOrigin.Begin)
                    CopyStreamToEnd(clsTmp, dest)
                End Using
            Finally
                IO.File.Delete(strTmp)
            End Try
        Else
            Using clsTmp As New IO.MemoryStream
                mEventArgs.Action = "Compressing"
                mEventArgs.ActionCompletedPercent = 0
                mEventArgs.ActionBytesProcessed = 0
                Manager.OnPackagingDaemonActivity(mEventArgs)
                Dim clsP As New LZMACompressProgress(src.Length)
                clsEncoder.Code(src, clsTmp, src.Length, Long.MaxValue, clsP)

                mEventArgs.Action = GetActivityCopyToDestinationActionText(job)
                mEventArgs.ActionCompletedPercent = 0
                mEventArgs.ActionBytesProcessed = 0
                Manager.OnPackagingDaemonActivity(mEventArgs)
                clsTmp.Seek(0, IO.SeekOrigin.Begin)
                CopyStreamToEnd(clsTmp, dest)
            End Using
        End If
    End Sub

    Private Class LZMACompressProgress
        Implements SevenZip.ICodeProgress
        Private mTotalInSize As Long
        Private mPercent As Byte = 0
        Private mLastTime As Date = Now
        Friend Sub New(ByVal totalInSize As Long)
            mTotalInSize = totalInSize
        End Sub

        Private Sub SetProgress(ByVal inSize As Long, ByVal outSize As Long) Implements SevenZip.ICodeProgress.SetProgress
            Dim bPercent As Byte = CByte(100 * inSize / mTotalInSize)
            mEventArgs.ActionBytesProcessed = inSize
            Dim blnUpdate As Boolean = False
            If bPercent <> mPercent Then
                blnUpdate = True
            ElseIf Now.Subtract(mLastTime).TotalSeconds > 1 Then
                blnUpdate = True
            End If
            If blnUpdate Then
                mLastTime = Now
                mPercent = bPercent
                mEventArgs.ActionCompletedPercent = bPercent
                Manager.OnPackagingDaemonActivity(mEventArgs)
            End If
        End Sub
    End Class

    Private Shared Function GetActivityCopyToDestinationActionText(ByVal job As Entities.Backup.Job) As String
        If job.CompressionProperties.Type = Entities.Backup.CompressionType.None Then
            Return "Copying"
        Else
            Return "Encrypting"
        End If
    End Function

    Private Shared Sub Compression_None_WriteToOutput(ByVal job As Entities.Backup.Job, ByVal dest As IO.Stream, ByVal src As IO.Stream)
        Framework.IO.BlockStreamWriter.WriteFixedLengthAscii(dest, "NONE")
        Framework.IO.BlockStreamWriter.Write(dest, src.Length)
        mEventArgs.Action = GetActivityCopyToDestinationActionText(job)
        CopyStreamToEnd(src, dest)
    End Sub
    Private Shared Sub CopyStreamToEnd(ByVal src As IO.Stream, ByVal dest As IO.Stream)
        Dim bPercent As Byte = 0
        Dim lTotalSize As Long = src.Length - src.Position
        Dim lTotalWritten As Long = 0
        Dim bBuffer(10240 - 1) As Byte
        Dim iLen As Integer
        iLen = src.Read(bBuffer, 0, bBuffer.Count)
        While iLen > 0
            dest.Write(bBuffer, 0, iLen)
            iLen = src.Read(bBuffer, 0, bBuffer.Count)
            lTotalWritten += iLen
            Dim iTmpPercent As Byte = CByte(100 * lTotalWritten / lTotalSize)
            If iTmpPercent <> bPercent Then
                bPercent = iTmpPercent
                mEventArgs.ActionCompletedPercent = bPercent
                mEventArgs.ActionBytesProcessed = lTotalWritten
                Manager.OnPackagingDaemonActivity(mEventArgs)
            End If
        End While
    End Sub

    Private Shared Function CreateMetaBlock(ByVal fileInfo As System.IO.FileInfo, ByVal version As Integer) As Byte()
        Using clsMemIo As New System.IO.MemoryStream
            Framework.IO.BlockStreamWriter.Write(clsMemIo, CShort(2)) 'block version
            With fileInfo
                Framework.IO.BlockStreamWriter.Write(clsMemIo, fileInfo.FullName)
                Framework.IO.BlockStreamWriter.Write(clsMemIo, fileInfo.Length)
                Framework.IO.BlockStreamWriter.Write(clsMemIo, CInt(fileInfo.Attributes))
                Framework.IO.BlockStreamWriter.Write(clsMemIo, fileInfo.CreationTimeUtc.ToString("g"))
                Framework.IO.BlockStreamWriter.Write(clsMemIo, fileInfo.LastWriteTimeUtc.ToString("g"))
                Framework.IO.BlockStreamWriter.Write(clsMemIo, fileInfo.LastAccessTimeUtc.ToString("g"))
                Framework.IO.BlockStreamWriter.Write(clsMemIo, version)
            End With
            Return clsMemIo.ToArray
        End Using
    End Function
    Private Shared Sub ParseMetaDataBlockAndFillInfo(ByVal block() As Byte, ByVal info As Entities.Backup.BackedUpFileInfoBase)
        Using clsMemIo As New System.IO.MemoryStream(block)
            Dim bsr As New BlockStreamReader(clsMemIo)

            Dim iVersion As Short = bsr.ReadShort
            info.FilePath = bsr.ReadString
            info.FileSize = bsr.ReadLong
            info.Attributes = CType(bsr.ReadInteger, System.IO.FileAttributes)
            info.CreationTimeUtc = Date.Parse(bsr.ReadString)
            info.LastWriteTimeUtc = Date.Parse(bsr.ReadString)
            Dim strDummy As String = bsr.ReadString
            If iVersion >= 2 Then
                info.Version = bsr.ReadInteger
            Else
                info.Version = 1
            End If
        End Using
    End Sub


    Private Shared Function CreateCryptoStreamAndWriteHeader(ByVal job As Entities.Backup.Job, ByVal destinationStream As IO.Stream) As IO.Stream
        If job.EncryptionProperties Is Nothing Then
            Framework.IO.BlockStreamWriter.WriteFixedLengthAscii(destinationStream, "NONE")
            Return destinationStream
        End If

        Select Case job.EncryptionProperties.Type
            Case Entities.Backup.EncryptionType.None
                Framework.IO.BlockStreamWriter.WriteFixedLengthAscii(destinationStream, "NONE")
                Return destinationStream
            Case Entities.Backup.EncryptionType.Rijndael
                Framework.IO.BlockStreamWriter.WriteFixedLengthAscii(destinationStream, "RIJN")
                Return CreateCryptoStream_RijndaelEncrypt(job, destinationStream)
            Case Else
                Throw New NotImplementedException
        End Select
    End Function
    Private Shared Function CreateCryptoStream_RijndaelEncrypt(ByVal job As Entities.Backup.Job, ByVal destinationStream As IO.Stream) As IO.Stream
        Dim bPassword() As Byte = System.Text.Encoding.ASCII.GetBytes(job.EncryptionProperties.Password)
        Dim bSalt() As Byte = {42, 99, 1, 243, 128, 33, 77, 80, 199, 3}
        Dim clsPwd As New System.Security.Cryptography.Rfc2898DeriveBytes(bPassword, bSalt, 2)
        Dim bKey() As Byte = clsPwd.GetBytes(CInt(256 / 8))

        Dim clsSymKey As New System.Security.Cryptography.RijndaelManaged
        clsSymKey.Mode = Security.Cryptography.CipherMode.CBC

        Dim clsEncryptor As System.Security.Cryptography.ICryptoTransform
        Dim bIV() As Byte = {121, 142, 9, 12, 43, 218, 183, 32, 254, 19, 54, 201, 55, 1, 17, 181}
        clsEncryptor = clsSymKey.CreateEncryptor(bKey, bIV)

        Return New System.Security.Cryptography.CryptoStream(destinationStream, clsEncryptor, Security.Cryptography.CryptoStreamMode.Write)
    End Function
    Private Shared Function CreateCryptoStream_RijndaelDecrypt(ByVal source As IO.Stream, ByVal password As String) As IO.Stream
        Dim bPassword() As Byte = System.Text.Encoding.ASCII.GetBytes(password)
        Dim bSalt() As Byte = {42, 99, 1, 243, 128, 33, 77, 80, 199, 3}
        Dim clsPwd As New System.Security.Cryptography.Rfc2898DeriveBytes(bPassword, bSalt, 2)
        Dim bKey() As Byte = clsPwd.GetBytes(CInt(256 / 8))

        Dim clsSymKey As New System.Security.Cryptography.RijndaelManaged
        clsSymKey.Mode = Security.Cryptography.CipherMode.CBC

        Dim clsDecryptor As System.Security.Cryptography.ICryptoTransform
        Dim bIV() As Byte = {121, 142, 9, 12, 43, 218, 183, 32, 254, 19, 54, 201, 55, 1, 17, 181}
        clsDecryptor = clsSymKey.CreateDecryptor(bKey, bIV)

        Return New System.Security.Cryptography.CryptoStream(source, clsDecryptor, Security.Cryptography.CryptoStreamMode.Read)
    End Function

    Private Class JobQueue
        Private Shared mWorkQueue As New Generic.List(Of WorkItem)
        Private Shared mCurrentWorkItem As WorkItem

        Public Shared Function GetNext() As WorkItem
            SyncLock mWorkQueue
                If mWorkQueue.Count = 0 Then
                    mCurrentWorkItem = Nothing
                Else
                    mCurrentWorkItem = mWorkQueue(0)
                    mWorkQueue.RemoveAt(0)
                End If
                mEventArgs.FilesInQueue = mWorkQueue.Count
            End SyncLock
            Manager.OnPackagingDaemonActivity(mEventArgs)
            Return mCurrentWorkItem
        End Function
        Public Shared Sub QueueWorkItem(ByVal job As Entities.Backup.Job, ByVal fileInfo As System.IO.FileInfo, ByVal storedVersions As Generic.List(Of Entities.Backup.StoredFile))
            Dim clsItem As New WorkItem
            clsItem.Job = job
            clsItem.FileInfo = fileInfo
            clsItem.StoredVersions = storedVersions
            SyncLock mWorkQueue
                mWorkQueue.Add(clsItem)
                mEventArgs.FilesInQueue = mWorkQueue.Count
            End SyncLock
            Manager.OnPackagingDaemonActivity(mEventArgs)
        End Sub
        Public Shared Function IsJobActive(ByVal job As Entities.Backup.Job) As Boolean
            SyncLock mWorkQueue
                For Each clsWI As WorkItem In mWorkQueue
                    If clsWI.Job.Id = job.Id Then Return True
                Next
                Dim clsWICurrent As WorkItem = mCurrentWorkItem
                If clsWICurrent IsNot Nothing Then
                    Return clsWICurrent.Job.Id = job.Id
                Else
                    Return False
                End If
            End SyncLock
        End Function
        Public Shared Sub WorkItemFinished(ByVal workItem As WorkItem)
            SyncLock mWorkQueue
                If mCurrentWorkItem Is workItem Then mCurrentWorkItem = Nothing
            End SyncLock
        End Sub
        Public Shared Sub AbortJob(ByVal job As Entities.Backup.Job)
            SyncLock mWorkQueue
                For i As Integer = mWorkQueue.Count - 1 To 0 Step -1
                    If mWorkQueue(i).Job.Id = job.Id Then mWorkQueue.RemoveAt(i)
                Next
                If mCurrentWorkItem IsNot Nothing AndAlso mCurrentWorkItem.Job.Id = job.Id Then
                    mCurrentWorkItem = Nothing
                    mDaemon.Abort(job)
                End If
            End SyncLock
        End Sub
    End Class
    Public Shared Sub QueueWorkItem(ByVal job As Entities.Backup.Job, ByVal fileInfo As System.IO.FileInfo, ByVal storedVersions As Generic.List(Of Entities.Backup.StoredFile))
        JobQueue.QueueWorkItem(job, fileInfo, storedVersions)
    End Sub
    Public Shared Function IsJobActive(ByVal job As Entities.Backup.Job) As Boolean
        Return JobQueue.IsJobActive(job)
    End Function
    Public Shared Sub AbortJob(ByVal job As Entities.Backup.Job)
        JobQueue.AbortJob(job)
    End Sub

    Private Class WorkItem
        Public Job As Entities.Backup.Job
        Public FileInfo As System.IO.FileInfo
        Public StoredVersions As Generic.List(Of Entities.Backup.StoredFile)
    End Class

    Private Shared Function SafeGetCompressionType(ByVal properties As Entities.Backup.CompressionProperties) As Entities.Backup.CompressionType
        If properties IsNot Nothing Then
            Return properties.Type
        Else
            Return Entities.Backup.CompressionType.None
        End If
    End Function

    Public Shared Function IsFileB2BBackupfile(ByVal filePath As String) As Boolean
        Using fs As New IO.FileStream(filePath, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
            'first 4 bytes are always "B2BB". Make sure this file has that!")
            If fs.Length < 4 Then Return False
            Dim strType As String = Framework.IO.BlockStreamReader.ReadAsciiChars(fs, 4)
            If strType <> "B2BB" Then Return False
            'next two bytes is the file structure version
            Dim iVersion As Short = Framework.IO.BlockStreamReader.ReadShort(fs)
            Return iVersion <= 1
        End Using
    End Function

    Public Shared Function IsFileEncrypted(ByVal filePath As String) As Boolean
        Using fs As New IO.FileStream(filePath, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
            'first 4 bytes is always "B2BB". Make sure this file has that!")
            If fs.Length < 4 Then Throw New FileParseException("File is not a B2B-backup file!")
            Dim strType As String = Framework.IO.BlockStreamReader.ReadAsciiChars(fs, 4)
            If strType <> "B2BB" Then Throw New FileParseException("File is not a B2B-backup file!")
            'next two bytes is the file structure version
            Dim iVersion As Short = Framework.IO.BlockStreamReader.ReadShort(fs)
            If iVersion <> 1 Then Throw New FileParseException("File structure unsupported!")

            'next 4 bytes are the encryption algorithm
            Dim strEncType As String = Framework.IO.BlockStreamReader.ReadAsciiChars(fs, 4)
            Return strType <> "NONE"
        End Using
    End Function

    Public Shared Function GetBackedUpFileContainerInfo(ByVal filePath As String, ByVal passwordIfNeeded As String) As Entities.Backup.BackedUpFileContainerInfo
        Using fs As New IO.FileStream(filePath, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
            'first 4 bytes is always "B2BB". Make sure this file has that!")
            If fs.Length < 4 Then Throw New FileParseException("File is not a B2B-backup file!")
            Dim strType As String = Framework.IO.BlockStreamReader.ReadAsciiChars(fs, 4)
            If strType <> "B2BB" Then Throw New FileParseException("File is not a B2B-backup file!")
            'next two bytes is the file structure version
            Dim iVersion As Short = Framework.IO.BlockStreamReader.ReadShort(fs)
            If iVersion <> 1 Then Throw New FileParseException("File structure unsupported!")

            Dim clsRet As New Entities.Backup.BackedUpFileContainerInfo
            clsRet.ContainerPath = filePath
            'next 4 bytes are the encryption algorithm
            Dim clsSrcStream As IO.Stream
            Dim strEncType As String = Framework.IO.BlockStreamReader.ReadAsciiChars(fs, 4)
            Select Case strEncType
                Case "NONE"
                    clsRet.Encryption = Entities.Backup.EncryptionType.None
                    clsSrcStream = fs
                Case "RIJN"
                    If String.IsNullOrEmpty(passwordIfNeeded) Then Throw New WrongPasswordException("Password not supplied!")
                    clsRet.Encryption = Entities.Backup.EncryptionType.Rijndael
                    clsSrcStream = CreateCryptoStream_RijndaelDecrypt(fs, passwordIfNeeded)
                Case Else
                    Throw New FileParseException("File has encryption method not supported!")
            End Select

            'Using clsSrcStream
            'now, read the metadatablock
            Dim bMetaData() As Byte
            Try
                Dim iLen As Integer = Framework.IO.BlockStreamReader.ReadInteger(clsSrcStream)
                bMetaData = Framework.IO.BlockStreamReader.ReadBytesFixedCount(clsSrcStream, iLen)
            Catch ex As ArithmeticException
                If clsRet.Encryption = Entities.Backup.EncryptionType.None Then
                    Throw New FileParseException("File is corrupt!")
                Else
                    Throw New WrongPasswordException("Password incorrect!")
                End If
            End Try
            ParseMetaDataBlockAndFillInfo(bMetaData, clsRet)

            'End Using
            clsRet.ContainerSize = fs.Length
            Return clsRet
        End Using
    End Function

    Friend Shared Sub RestoreFile(ByVal args As RestoreTask.ProgressEventArgs, ByVal passwordIfNeeded As String, ByVal task As RestoreTask)
        args.CurrentFileSizeRestored = 0
        args.CurrentTask = "Unpacking file"
        task.OnProgress()

        Using fs As New IO.FileStream(args.CurrentSourceFilePath, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
            'first 4 bytes is always "B2BB". Make sure this file has that!")
            If fs.Length < 4 Then Throw New FileParseException("File is not a B2B-backup file!")
            Dim strType As String = Framework.IO.BlockStreamReader.ReadAsciiChars(fs, 4)
            If strType <> "B2BB" Then Throw New FileParseException("File is not a B2B-backup file!")
            'next two bytes is the file structure version
            Dim iVersion As Short = Framework.IO.BlockStreamReader.ReadShort(fs)
            If iVersion <> 1 Then Throw New FileParseException("File structure unsupported!")

            Dim clsMD As New Entities.Backup.BackedUpFileContainerInfo

            'next 4 bytes are the encryption algorithm
            Dim clsSrcStream As IO.Stream
            Dim strEncType As String = Framework.IO.BlockStreamReader.ReadAsciiChars(fs, 4)
            Select Case strEncType
                Case "NONE"
                    clsMD.Encryption = Entities.Backup.EncryptionType.None
                    clsSrcStream = fs
                Case "RIJN"
                    If String.IsNullOrEmpty(passwordIfNeeded) Then Throw New WrongPasswordException("Password not supplied!")
                    clsMD.Encryption = Entities.Backup.EncryptionType.Rijndael
                    clsSrcStream = CreateCryptoStream_RijndaelDecrypt(fs, passwordIfNeeded)
                Case Else
                    Throw New FileParseException("File has encryption method not supported!")
            End Select

            'Using clsSrcStream
            'now, read the metadatablock
            Dim bMetaData() As Byte
            Try
                Dim iLen As Integer = Framework.IO.BlockStreamReader.ReadInteger(clsSrcStream)
                bMetaData = Framework.IO.BlockStreamReader.ReadBytesFixedCount(clsSrcStream, iLen)
            Catch ex As ArithmeticException
                If clsMD.Encryption = Entities.Backup.EncryptionType.None Then
                    Throw New FileParseException("File is corrupt!")
                Else
                    Throw New WrongPasswordException("Password incorrect!")
                End If
            End Try
            ParseMetaDataBlockAndFillInfo(bMetaData, clsMD)
            clsMD.ContainerSize = fs.Length

            Using clsDest As New IO.FileStream(args.CurrentDestinationFilePath, IO.FileMode.OpenOrCreate, IO.FileAccess.Write)

                'now, read compression type
                Dim strCompression As String = BlockStreamReader.ReadAsciiChars(clsSrcStream, 4)
                Dim lDataLength As Long = BlockStreamReader.ReadLong(clsSrcStream)
                Select Case strCompression
                    Case "NONE"
                        Dim lCompleted As Long = 0
                        Dim bBuffer(10240 - 1) As Byte
                        Dim iLen As Integer = clsSrcStream.Read(bBuffer, 0, bBuffer.Count - 1)
                        While iLen > 0
                            clsDest.Write(bBuffer, 0, iLen)
                            lCompleted += iLen
                            args.CurrentFileSizeRestored = lCompleted
                            task.OnProgress()
                            iLen = clsSrcStream.Read(bBuffer, 0, bBuffer.Count - 1)
                        End While
                    Case "LZMA"
                        'first, read 5 bytes with coder properties
                        Dim bProperties() As Byte = BlockStreamReader.ReadBytesFixedCount(clsSrcStream, 5)
                        'and read length of decompressed file
                        Dim lLen As Long = BlockStreamReader.ReadLong(clsSrcStream)

                        Dim clsDecoder As New SevenZip.Compression.LZMA.Decoder
                        clsDecoder.SetDecoderProperties(bProperties)

                        clsDecoder.Code(clsSrcStream, clsDest, lDataLength, lLen, task)
                End Select
            End Using
        End Using
        args.CurrentFileSizeRestored = args.CurrentFileSize
        task.OnProgress()
    End Sub


End Class

Public Class FileParseException
    Inherits Exception
    Public Sub New(ByVal message As String)
        MyBase.New(message)
    End Sub
End Class
Public Class WrongPasswordException
    Inherits Exception
    Public Sub New(ByVal message As String)
        MyBase.New(message)
    End Sub
End Class
